/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Basic inner operations for building symbol tables.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include "_stb_inner.h"


// -----------------------------------------------------------------------
//    Functions for scope-stack
//

static
t_stack_item_ptr stb_stack_new_item(t_symtable_ptr  symtablePtr,
                                    t_funcinfo_ptr  currFuncInfoPtr)
{
    t_stack_item_ptr itemPtr = calloc(1, sizeof(t_stack_item));
    if (NULL == itemPtr) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }
    itemPtr->symtablePtr = symtablePtr;
    itemPtr->currFuncInfoPtr = currFuncInfoPtr;
    return itemPtr;
}

/*
void stb_free_stack_item(t_stack_item_ptr itemPtr) {
    free(itemPtr);
}
*/
void stb_stack_push(t_symtable_builder* builderPtr,
                    t_symtable_ptr  symtablePtr,
                    t_funcinfo_ptr  currFuncInfoPtr)
{
    t_stack_item_ptr itemPtr = stb_stack_new_item(symtablePtr, currFuncInfoPtr);
    stack_push( &(builderPtr->scopeStack), itemPtr);
}

t_stack_item_ptr stb_stack_top(t_symtable_builder* builderPtr) {
    return (t_stack_item_ptr)stack_top(&(builderPtr->scopeStack));
}

void stb_stack_pop(t_symtable_builder* builderPtr) {
    t_stack_item_ptr itemPtr = (t_stack_item_ptr)stack_pop(&(builderPtr->scopeStack));
    free(itemPtr);
}

void stb_free_funcinfo(t_funcinfo_ptr infoPtr) {
    list_clean( &(infoPtr->undefinedLabels) );
    free (infoPtr);
}

t_funcinfo_ptr stb_new_funcinfo(EnumTokenKind retTypeId,
                                t_symtable_ptr funcSymTable,
                                const char * funcName)
{
    t_funcinfo_ptr infoPtr = calloc(1, sizeof(t_funcinfo));
    if (NULL == infoPtr) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_func_symInfo_ptr  pFuncSymInfo = calloc(1,sizeof(t_func_symInfo));
    if (NULL == pFuncSymInfo) {
        ERROR_REPORT_MALLOC();
        free (infoPtr);
        return NULL;
    }

    infoPtr->returnType     = retTypeId;
    infoPtr->funcName       = funcName;
    infoPtr->funcSymInfoPtr = pFuncSymInfo;
    infoPtr->symTablePtr    = funcSymTable;
    list_init(&(infoPtr->undefinedLabels), NULL);

    pFuncSymInfo->symTablePtr  = funcSymTable;
    list_init(&(pFuncSymInfo->parameterList), NULL);

    return infoPtr;
}



// -----------------------------------------------------------------------
// concrete attribute class for scalar variables, arrays, parameters
//


// helper for builder.nodeAttributes:
// free key and value in treemap
void stb_attrset_free(t_map_key K, t_map_value V)
{
    if (NULL == V) return;

    ref_free_((t_reference_ptr) V);
}



// helper to destroy user-defined attribute object of variable and array
static void free_attr_directDeclarator(t_reference_ptr attrBasic) {
    t_attr_directDeclarator * attr = (t_attr_directDeclarator*)attrBasic;
    if ( NULL == attr )  return;
    if ( NULL != attr->dimensions ) {
        free(attr->dimensions);
        attr->dimensions = NULL;
    }
    if ( NULL != attr->initExprs ) {
        list_destroy(attr->initExprs);
        attr->initExprs = NULL;
    }
}


// helper to creates a new attribute of variable and array
t_attr_directDeclarator * new_attr_directDeclarator() {
    t_attr_directDeclarator * attr = (t_attr_directDeclarator*) calloc (
            1, sizeof (t_attr_directDeclarator));
    ref_init_((t_reference_ptr) attr, free_attr_directDeclarator);
    return attr;
}

static void stb_free_initExpr_(t_list_app_value initExpr) {
    t_init_expr_ptr ptr = (t_init_expr_ptr)initExpr;
    free(ptr->indexHolders);
}

t_list_ptr new_init_expr() {
    return list_new(stb_free_initExpr_);
}


// -----------------------------------------------------------------------
// some helper functions
//

/*
#define INIT_COUNT(n)  (((n)<=2) ? 1 : (n)/2)

//  check the number of initializer against to the length of Kth dimension.
static bool check_initializer_number_(
        int K ,           // which dimension, starting from 1
        const int * dimensions, // dimensions info from the type of array
        t_tree_ptr initializerNode)
{
    if (NULL == initializerNode)
        return false;

    bool hasError = false;
    int  arrDimension = (dimensions==NULL ? 0 : dimensions[0]);

    // Initializer is a list like { V1 , V2 , ... , Vn }
    //                    or like { }
    //             or only one value
    int     nChildren = tree_get_childCount(initializerNode);
    int  nInitializer = INIT_COUNT(nChildren);

    int  dimLength = 1;
    if ( K <= arrDimension)
        dimLength = dimensions[K];
    else if ( K == arrDimension + 1) {
        dimLength = 1;
    }
    else
        dimLength = 0;

    t_position    pos = tree_get_startPosition(initializerNode);
    if (nInitializer > dimLength) {
        if ( K > arrDimension + 1) {
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_INIT_TOODEEP,  K, arrDimension + 1);
        } else {
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_INIT_TOOLEN,
                               nInitializer, dimLength, K);
        }

        // DO NOT return to continue to check deeply
        hasError = true;
        nInitializer = dimLength;
    }

    t_tree_ptr  subInitNode;
    for ( int n = 0; n < nInitializer; ++ n ) {
        // one initializer of next level
        subInitNode = tree_get_child(initializerNode, n*2+1);
        if ( true == check_initializer_number_(K+1, dimensions, subInitNode) )
            hasError = true;
    }

    return hasError;
}


// Check the length of each dimension according to initializer,
//   and reset the length of first dimension if its length is unknown.
//
// Return true for any error.
static bool check_dimLength_(t_attr_directDeclarator * attr,
                             t_type_ptr  baseType)
{
//    assert( attr->dimensions[0] > 0 );
    assert( NULL != attr->initializerNode );
    t_tree_ptr initializerNode = attr->initializerNode;

    // Initializer is a list like { V1 , V2 , ... , Vn }
    //                    or like { }
    //             or only one value
    int     nChildren = tree_get_childCount(initializerNode);
    int  nInitializer = INIT_COUNT(nChildren);

    // fill in the element number of first dimension if it is unknown
    if ( NULL != attr->dimensions && attr->dimensions[1] < 1) {
        attr->dimensions[1] = nInitializer;
    }

    // check each dimension
    return check_initializer_number_(1,  attr->dimensions, initializerNode);
}


// Check elements number of each dimension for array definition.
// If this array is a formal parameter of function, the first dimension
// may be omitted. Otherwise, the size of each dimension must be
// specified as a positive integer.
//
// Return false only if any dimension is bad.
bool stb_check_array_dimensions(t_attr_directDeclarator * attr,
                                t_type_ptr  baseType ,
                                bool isParameter     )
{
    t_token_ptr  idToken   = tree_get_token(attr->idNode);
    t_position_ptr   pos   = token_get_position(idToken);
    size_t max_nElements   = 0x7FFFFFU;
    size_t nElements       = 1;
    bool   nElementsTooBig = false;
    bool   hasError        = false;

    if ( NULL != attr->initializerNode ) {
        if ( check_dimLength_(attr, baseType) )
            hasError = true;
    }

    int    dimCounter = 1;
    for( int dimLength;
         dimCounter <= ARRAY_DIMS(attr->dimensions);
         ++ dimCounter )
    {
        if (NULL == attr->dimensions)
            dimLength = 1;
        else
            dimLength = attr->dimensions[dimCounter];

        if (     (dimLength < 0)
              || (dimLength == 0 && dimCounter > 1)
              || (dimLength == 0 && dimCounter == 1 && ! isParameter)
           )
        {
            hasError = true;
            ERROR_REPORT_INPUT(pos->filePath, pos->line, pos->column,
                               EC_DIMSZ_BAD,  dimLength, dimCounter);

            // ignore the error for continue to analysis
            if (NULL != attr->dimensions)
                attr->dimensions[dimCounter] = 1;
        }
        else if (dimLength > 0 ) {
            if (nElements > max_nElements / dimLength )
                nElementsTooBig = true;
            else
                nElements *= dimLength;
        }
    }

    if( nElementsTooBig ) {
        hasError = true;
        ERROR_REPORT_INPUT(pos->filePath, pos->line, pos->column,
                           EC_DIM_TOOBIG, token_get_text(idToken));
    }

    attr->hasError = hasError;
    return ! hasError;
}
*/

// Simulates memory allocation in real compiler.
// AMCC uses following simple rules ONLY for demonstration:
// (1) A scalar or array always occupies a contiguous memory block;
// (2) Takes no account of word alignment rules and machine factors;
// (3) Treats an array in parameter list as a simple pointer.
//
void stb_allocMemoryForVariable(t_symtable_ptr symTable,
                                t_type_ptr     varType,
                                size_t * pOffset /*output*/ )
{
    size_t  sizeTotal = type_width(varType);
    *pOffset = symTable->totalSizeOfVariables;
    symTable->totalSizeOfVariables += sizeTotal;
}



// -----------------------------------------------------------------------
//          Functions for name binding
//


// try binding all un-defined labels to their definitions
// at the end of current function.
void stb_resolveAllUndefinedLabels(t_list_ptr  undefinedLabels,
                                   t_symtable_ptr funcSymTable)
{
    t_list_iterator  itr;
    LIST_ITERATOR_INIT (&itr, undefinedLabels );
    t_list_node_ptr e   = LIST_ITERATOR_BEGIN( & itr );
    t_list_node_ptr end = LIST_ITERATOR_END  ( & itr );
    for ( ; e != end; e = LIST_ITERATOR_NEXT ( & itr ) ) {
        t_tree_ptr     pIdNode        = (t_tree_ptr) list_node_get_value(e);
        t_token_ptr    pIdToken       = tree_get_token(pIdNode);
        t_position_ptr pIdPosition    = token_get_position(pIdToken);
        t_symentry_ptr pLabelSymEntry = symtable_find_local(
                funcSymTable,  pIdNode);
        if (NULL != pLabelSymEntry) {
            stb_bindLabelReferToDef(pIdNode, pLabelSymEntry);
        } else {
            ERROR_REPORT_INPUT(pIdPosition->filePath, pIdPosition->line,
                               pIdPosition->column, EC_UNDEFINED,
                               "label", token_get_text(pIdToken));
        }
    }

    list_clean( undefinedLabels );
}


// try binding all un-defined functions to their definition.
void stb_resolveAllUndefinedFunctions(t_list_ptr undefinedFunctions,
                                      t_symtable_ptr symTableStarting)
{
    t_list_iterator  itr;
    LIST_ITERATOR_INIT (&itr, undefinedFunctions );
    t_list_node_ptr e   = LIST_ITERATOR_BEGIN( & itr );
    t_list_node_ptr end = LIST_ITERATOR_END  ( & itr );
    for ( ; e != end; e = LIST_ITERATOR_NEXT ( & itr ) ) {
        // each element is the functionCall node for production:
        //  functionCall -> TK_ID   '(' argumentExpressionList ? ')'
        t_tree_ptr     pCallNode      = (t_tree_ptr) list_node_get_value(e);
        t_tree_ptr     pIdNode        = tree_get_child(pCallNode, 0);
        t_token_ptr    pIdToken       = tree_get_token(pIdNode);
        t_position_ptr pIdPosition    = token_get_position(pIdToken);
        t_symentry_ptr pFuncSymEntry  = symtable_find(symTableStarting,  pIdNode);
        if (NULL != pFuncSymEntry) {
            stb_bindFunctionReferToDef(pIdNode, pFuncSymEntry, pCallNode);
        } else {
            ERROR_REPORT_INPUT(pIdPosition->filePath, pIdPosition->line,
                               pIdPosition->column, EC_UNDEFINED,
                               "function", token_get_text(pIdToken));
        }
    }

    list_clean( undefinedFunctions );
}


// Bind a variable reference to its definition
bool stb_bindVariableReferToDef(t_tree_ptr pReferNode,
                                t_symentry_ptr pSymEntry,
                                t_tree_ptr leftValueNode)
{
    t_token_ptr    pIdToken = tree_get_token( pReferNode );
    t_position_ptr pRefPos  = token_get_position(pIdToken );
    const char *   varName  = token_get_text(pIdToken);

    // definitionTreeNode must be marked the variable name in its definition
    if( ! tree_is_varNameDefinition(pSymEntry->definitionTreeNode) ) {
        ERROR_REPORT_INPUT(pRefPos->filePath, pRefPos->line,
                           pRefPos->column, EC_REFER_KIND,
                           varName, "variable");
        return false;
    }

    // binding the node referenced to an ID to its symtable entry
    tree_set_symbolEntry( pReferNode, pSymEntry );

    t_position pDefPos = tree_get_startPosition(pSymEntry->definitionTreeNode);
    LOG_STEP ("*** BIND-VAR: use [%s] at [%d:%d] --> define at [%d:%d] --> symEntry [%tx]",
              varName, pRefPos->line, pRefPos->column,
              pDefPos.line, pDefPos.column,  pSymEntry );
    return true;
}


// Bind a label reference to its definition
bool stb_bindLabelReferToDef(t_tree_ptr pReferNode,
                             t_symentry_ptr pSymEntry)
{
    t_token_ptr    pIdToken    = tree_get_token( pReferNode );
    t_position_ptr pRefPos     = token_get_position(pIdToken );
    const char *   labelName   = token_get_text(pIdToken);

    // definitionTreeNode must be marked the label name in its definition
    if ( ! tree_is_labelNameDefinition(pSymEntry->definitionTreeNode) ) {
        ERROR_REPORT_INPUT(pRefPos->filePath, pRefPos->line,
                           pRefPos->column, EC_REFER_KIND,
                           labelName, "label");
        return false;
    }

    tree_set_symbolEntry(pReferNode, pSymEntry);

    t_position pDefPos = tree_get_startPosition(pSymEntry->definitionTreeNode);
    LOG_STEP ("*** BIND-LABEL: use [%s] at [%d:%d] --> define at [%d:%d] --> symEntry [%tx]",
              labelName, pRefPos->line, pRefPos->column,
              pDefPos.line, pDefPos.column,  pSymEntry );
    return true;
}


// The type of each actual argument is inferred in analyzing expression,
// so, this function just returns the type.
static  t_type_ptr calc_argument_type(t_list_ptr  argList, int idx) {
    t_tree_ptr argNode = (t_tree_ptr)list_get_value(argList, idx);
    t_type_ptr argType = EXPRTYPE_GET( argNode );
    return argType;
}

// Bind a function reference to its definition
bool stb_bindFunctionReferToDef(t_tree_ptr     pReferNode,
                                t_symentry_ptr pSymEntry,
                                t_tree_ptr     pCallNode)
{
    t_token_ptr    pIdToken    = tree_get_token( pReferNode );
    t_position_ptr pRefPos     = token_get_position(pIdToken );
    const char *   funcName    = token_get_text(pIdToken);
    t_position defPos = tree_get_startPosition(pSymEntry->definitionTreeNode);

    // definitionTreeNode must be marked by the function name of its definition
    if ( ! tree_is_funcNameDefinition(pSymEntry->definitionTreeNode) ) {
        ERROR_REPORT_INPUT(pRefPos->filePath, pRefPos->line,
                           pRefPos->column, EC_REFER_KIND,
                           funcName, "function");
        return false;
    }

// check/compare the number of actual arguments and formal parameters

    // get actual arguments
    //   production:  functionCall -> TK_ID   '(' argumentList ')'
    int         nArguments   = 0;     // the number of real arguments
    t_tree_ptr  pArgListNode = tree_get_child(pCallNode, 2);
    if (NULL != pArgListNode) {
        //  production: argumentList -> EPSILON | argument ( , argument)*
        t_tree_ptr firstChild = tree_get_child(pArgListNode, 0);
        if ( tree_is_epsilon(firstChild) )
            nArguments = 0 ;
        else {
            nArguments = tree_get_childCount(pArgListNode);
            nArguments = 1 + (nArguments - 1) / 2;
        }
    }

    // check the number of actual arguments is same as formal parameters
    t_list_ptr  paramList = &(pSymEntry->content.funcInfo->parameterList);
    int       nParameters = list_size( paramList );
    if ( nParameters != nArguments ) {
        ERROR_REPORT_INPUT(pRefPos->filePath, pRefPos->line, pRefPos->column,
                           EC_FUNCALL_NUM,
                           nParameters,  (nParameters > 1 ? "s" : ""),
                           funcName,  defPos.line,   defPos.column);
        return false;
    }

    // check the compatibility for types of arguments --> parameters
    if (nParameters > 0) {
        bool typeIsCompatible;
        t_type_ptr pParamType,  pArgType;
        t_list_ptr argList =  tree_get_children(pArgListNode);
        int   argIdx ;
        for (int k = 0; k < nParameters; ++k) {
            t_symentry_ptr pParamSymEntry = list_get_value(paramList, k);
            pParamType = symentry_type(pParamSymEntry);
            argIdx     = (k==0 ? 0 : 2*k);
            pArgType   = calc_argument_type(argList, argIdx);
            typeIsCompatible = type_is_compatible(pParamType, pArgType, true);
            if (false == typeIsCompatible) {
                t_tree_ptr argNode = (t_tree_ptr)list_get_value(argList, argIdx);
                t_position  argPos = tree_get_startPosition(argNode);
                ERROR_REPORT_INPUT(argPos.filePath, argPos.line, argPos.column,
                                   EC_FUNCALL_TYPE,
                                   type_name(pArgType),
                                   type_name(pParamType),  k + 1,
                                   funcName, defPos.line, defPos.column);
                return false;
            }
        }
    }


    tree_set_symbolEntry(pReferNode, pSymEntry);

    LOG_STEP ("*** BIND-FUNC: use [%s] at [%d:%d] --> define at [%d:%d] --> symEntry [%tx]",
              funcName, pRefPos->line, pRefPos->column,
              defPos.line, defPos.column, pSymEntry );
    return true;
}


